#include "paging_api.h"
//#include "exceptions.h"
#include "testing.h"
//#include "helpers.h"
#include "prot.h"
#include "exceptions.h"
#include "handlers.h"


#pragma GCC push_options
#pragma GCC optimize ("O0")

#ifdef __KERNEL__
extern struct interrupts_data INTERRUPTS;
struct interrupts_data *PINTERRUPTS = &INTERRUPTS;
#else
struct interrupts_data *PINTERRUPTS = INTERRUPTS_STATIC_ADDRESS;
#endif

struct registers_state REGISTERS;

u64 debug;


void test_context(void) {
  save_context(&(TESTER.linux_context));
  load_context(&(TESTER.test_context));
}

void linux_context(void) {
  load_context(&(TESTER.linux_context));
}



inline char * get_virt (struct test_command *cmd) {
  if (PAGE_UNDEFINED == cmd->page)
    return 0;
  
  return (char *) ((u64)TESTER.pages[cmd->page].virt |
         (cmd->vaddr & TESTER.pages[cmd->page].offset_mask) );
}

inline u64 get_vaddr (struct test_command *cmd) {
  if (PAGE_UNDEFINED == cmd->page)
    return cmd->vaddr;
  
  return TESTER.pages[cmd->page].window_offset + cmd->vaddr;
}

int is_change_address(struct test_command *c) {
  return c->cmd == CMD_WRITE || c->cmd == CMD_RUN;
}

int is_system_cmd (struct test_command *c) {
  return c->cmd == CMD_MAP || c->cmd == CMD_FLAGS;
}



/*
 * int3 for CMD_RUN
 */
void preprocess (struct test_command *c) {
  char *p = get_virt(c);
  
  if (c->cmd == CMD_RUN && p) {
    *p = 0xCC;
  }
}


void handle_addr_cmd (struct test_command *c)
{
  u64 vaddr = get_vaddr(c);
  preprocess (c);
  
  switch (c->cmd) {
    case CMD_READ:
      asm volatile("movb (%0), %%al" : : "r" (vaddr) : "%al");
      break;
    case CMD_WRITE:
      asm volatile("movb $123, (%0)" : : "r" (vaddr));
      break;
    case CMD_RUN:
      asm volatile("jmp *%0" : : "r" (vaddr));
      break;
    case CMD_INV:
      asm volatile("invlpg (%0)" : : "r" (vaddr));
      break;
  }
}



void handle_system_cmd (struct test_command *c) {
  switch (c->cmd) {
    case CMD_MAP:
      map64(TESTER.pages[c->page].virt, c->pos, c->addr_mask, TESTER.pages[c->page2].phys);
      break;
    case CMD_FLAGS:
      flags64(TESTER.pages[c->page].virt, c->pos, c->flags, c->uflags);
      break;
  }
}



/*
 * if no interrupts TESTER.cmd_ind will be incremented
 */
void handle (struct test_command *c) {
  if (is_system_cmd(c))
    handle_system_cmd(c);
  else
    handle_addr_cmd(c);
}


void save_int_info (void) {
	memcpy (TESTER.result, &INTERRUPTS.int_info, sizeof(INTERRUPTS.int_info));
	memset (&INTERRUPTS.int_info, 0, sizeof(INTERRUPTS.int_info));
  
	TESTER.result += sizeof(INTERRUPTS.int_info);
}

struct test_command * get_current_command (void) {
  return TESTER.commands + TESTER.cmd_ind;
}

int handle_until_flush (void) {
  struct test_command * c;
  
	INTERRUPT_ANCHOR("go");
	
	c = get_current_command();
	if (! c->has_result) {
		// unpredicted interrupt
		return 1;
	}
	
	asm volatile("jmp save");
	
  asm volatile("go:");
  
  c = get_current_command();
  if (c->cmd == CMD_FLUSH)
    return 0;
  
  handle (c);
	
	asm volatile("save:");
	
  if (c->has_result)
    save_int_info();
  
  TESTER.cmd_ind ++;
  asm volatile("jmp go");
  
  return 0; //for gcc not to warn
}

int run_until_flush (void)
{
  int res;
	//PINTERRUPTS = &INTERRUPTS;
//   printk("<1> SWITCH TO TEST CONTEXT\n");
  test_context();
    /// DON'T return until linux_context() !!!
  
    res = handle_until_flush ();
    if (0 == res)
      TESTER.cmd_ind ++; //handle CMD_FLUSH
  
  linux_context();
//   printk("<1> SWITCH TO LINUX CONTEXT\n");
  
  return res;
}


int run_test (void)
{
  TESTER.result = TESTER.results;
  if ( get_current_command()->cmd != CMD_END && run_until_flush() != 0 )
    return RT_ERROR;
  
  return get_current_command()->cmd == CMD_END ? RT_END : RT_OK;
}


#pragma GCC pop_options
